import sqlite3 from 'sqlite3'
import path from 'path'
import { parseDriveUrl } from '../../lib/urls'
import { cbPromise } from '../../lib/functions'
import { setupSqliteDB } from '../lib/db'

// globals
// =

var db
var migrations
var setupPromise

// exported methods
// =

/**
 * @param {Object} opts
 * @param {string} opts.userDataPath
 */
export function setup (opts) {
  // open database
  var dbPath = path.join(opts.userDataPath, 'SiteData')
  db = new sqlite3.Database(dbPath)
  setupPromise = setupSqliteDB(db, {migrations}, '[SITEDATA]')
}

/**
 * @param {string} url
 * @param {string} key
 * @param {number | string} value
 * @param {Object} [opts]
 * @param {boolean} [opts.dontExtractOrigin]
 * @param {boolean} [opts.normalizeUrl]
 * @returns {Promise<void>}
 */
export async function set (url, key, value, opts) {
  await setupPromise
  var origin = opts && opts.dontExtractOrigin ? url : await extractOrigin(url)
  if (!origin) return null
  if (opts && opts.normalizeUrl) origin = normalizeUrl(origin)
  return cbPromise(cb => {
    db.run(`
      INSERT OR REPLACE
        INTO sitedata (origin, key, value)
        VALUES (?, ?, ?)
    `, [origin, key, value], cb)
  })
}

/**
 * @param {string} url
 * @param {string} key
 * @returns {Promise<void>}
 */
export async function clear (url, key) {
  await setupPromise
  var origin = await extractOrigin(url)
  if (!origin) return null
  return cbPromise(cb => {
    db.run(`
      DELETE FROM sitedata WHERE origin = ? AND key = ?
    `, [origin, key], cb)
  })
}

/**
 * @param {string} url
 * @param {string} key
 * @param {Object} [opts]
 * @param {boolean} [opts.dontExtractOrigin]
 * @param {boolean} [opts.normalizeUrl]
 * @returns {Promise<string>}
 */
export async function get (url, key, opts) {
  await setupPromise
  var origin = opts && opts.dontExtractOrigin ? url : await extractOrigin(url)
  if (!origin) return null
  if (opts && opts.normalizeUrl) origin = normalizeUrl(origin)
  return cbPromise(cb => {
    db.get(`SELECT value FROM sitedata WHERE origin = ? AND key = ?`, [origin, key], (err, res) => {
      if (err) return cb(err)
      cb(null, res && res.value)
    })
  })
}

/**
 * @param {string} url
 * @param {string} key
 * @returns {Promise<string>}
 */
export function getPermission (url, key) {
  return get(url, 'perm:' + key)
}

/**
 * @param {string} url
 * @returns {Promise<Object>}
 */
export async function getPermissions (url) {
  await setupPromise
  var origin = await extractOrigin(url)
  if (!origin) return null
  return cbPromise(cb => {
    db.all(`SELECT key, value FROM sitedata WHERE origin = ? AND key LIKE 'perm:%'`, [origin], (err, rows) => {
      if (err) return cb(err)

      // convert to a dictionary
      // TODO - pull defaults from browser settings
      var perms = { /* js: true */ }
      if (rows) rows.forEach(row => { perms[row.key.slice('5')] = row.value })
      cb(null, perms)
    })
  })
}

/**
 * @param {string} url
 * @returns {Promise<Array<string>>}
 */
export async function getNetworkPermissions (url) {
  await setupPromise
  var origin = await extractOrigin(url)
  if (!origin) return null
  return cbPromise(cb => {
    db.all(`SELECT key, value FROM sitedata WHERE origin = ? AND key LIKE 'perm:network:%'`, [origin], (err, rows) => {
      if (err) return cb(err)

      // convert to array
      var origins = /** @type string[] */([])
      if (rows) {
        rows.forEach(row => {
          if (row.value) origins.push(row.key.split(':').pop())
        })
      }
      cb(null, origins)
    })
  })
}

/**
 * @param {string} url
 * @param {string} key
 * @param {string | number} value
 * @returns {Promise<void>}
 */
export function setPermission (url, key, value) {
  value = value ? 1 : 0
  return set(url, 'perm:' + key, value)
}

/**
 * @param {string} url
 * @param {string} key
 * @returns {Promise<void>}
 */
export function clearPermission (url, key) {
  return clear(url, 'perm:' + key)
}

/**
 * @param {string} key
 * @returns {Promise<void>}
 */
export async function clearPermissionAllOrigins (key) {
  await setupPromise
  key = 'perm:' + key
  return cbPromise(cb => {
    db.run(`
      DELETE FROM sitedata WHERE key = ?
    `, [key], cb)
  })
}

export const WEBAPI = {
  get,
  set,
  getPermissions,
  getPermission,
  setPermission,
  clearPermission,
  clearPermissionAllOrigins
}

// internal methods
// =

/**
 * @param {string} originURL
 * @returns {Promise<string>}
 */
async function extractOrigin (originURL) {
  var urlp = parseDriveUrl(originURL)
  if (!urlp || !urlp.host || !urlp.protocol) return
  return (urlp.protocol + urlp.host + (urlp.port ? `:${urlp.port}` : ''))
}

/**
 * @param {string} originURL
 * @returns {string}
 */
function normalizeUrl (originURL) {
  try {
    var urlp = new URL(originURL)
    return (urlp.protocol + '//' + urlp.hostname + (urlp.port ? `:${urlp.port}` : '') + urlp.pathname).replace(/([/]$)/g, '')
  } catch (e) {}
  return originURL
}

migrations = [
  // version 1
  // - includes favicons for default bookmarks
  function (cb) {
    db.exec(`
      CREATE TABLE sitedata(
        origin NOT NULL,
        key NOT NULL,
        value
      );
      CREATE UNIQUE INDEX sitedata_origin_key ON sitedata (origin, key);
      INSERT OR REPLACE INTO "sitedata" VALUES('https:duckduckgo.com','favicon','');
      PRAGMA user_version = 1;
    `, cb)
  },
  // version 2
  // - more favicons for default bookmarks (removed)
  function (cb) {
    db.exec(`PRAGMA user_version = 2;`, cb)
  },
  // version 3
  // - more favicons for default bookmarks (removed)
  function (cb) {
    db.exec(`PRAGMA user_version = 3;`, cb)
  },
  // version 4
  // - more favicons for default bookmarks (removed)
  function (cb) {
    db.exec(`PRAGMA user_version = 4;`, cb)
  },
  // version 5
  // - more favicons for default bookmarks (removed)
  function (cb) {
    db.exec(`PRAGMA user_version = 5;`, cb)
  },
  // version 6
  // - more favicons (removed)
  function (cb) {
    db.exec(`PRAGMA user_version = 6;`, cb)
  },
  // version 7
  // - more favicons
  function (cb) {
    db.exec(`
      INSERT OR REPLACE INTO "sitedata" VALUES('https:beaker.network','favicon','');
      INSERT OR REPLACE INTO "sitedata" VALUES('https:hyperdrive.network','favicon','');
      PRAGMA user_version = 7;
   `, cb)
  }
]
